//This file is a part of StoragePlug for Minecraft.
//http://code.google.com/p/minecraft-storageplug/
//StoragePlug is Copyright (C) 2011 Christopher Smith
//Released under the MIT license:
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights 
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//DEALINGS IN THE SOFTWARE.
//
//Related to Minecraft Copyright (C) 2009-2011 Mojang http://www.minecraft.net/

package net.minecraft.storageplug;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import net.minecraft.storageplug.mcregion.MCRegionStorageProvider;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class PluginLoader
{
	ClassLoader loader;
	
	private PluginLoader()
	{
		File plugindir = new File("storageplug");
		
		try
		{
			loader = mkClassLoader(plugindir);
		} catch(Exception e)
		{
			System.err.println("Storageplug: Error building classloader for plugin jars");
			e.printStackTrace();
		}
	}
	
	ClassLoader mkClassLoader(File dir)
		throws Exception
	{
		ArrayList<URL> jars = new ArrayList<URL>();

		includeDir(jars, dir);
		
		return URLClassLoader.newInstance(jars.toArray(new URL[0]));
	}
	
	void includeDir(ArrayList<URL> jars, File dir)
		throws Exception
	{
		for(File f : dir.listFiles())
		{
			if(f.isFile() && f.getName().endsWith(".jar"))
			{
				System.out.println("StoragePlug: Including jar " + f.getName());
				jars.add(f.toURI().toURL());
			}
			
			if(f.isDirectory())
				includeDir(jars, f);
		}
	}
	
	public StorageProvider GetStorageInterface(File storage_dir)
		throws Exception
	{
		File configfile = new File(storage_dir, "storageplug.xml");
		
		if(configfile.exists())
		{		
	        DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
	        DocumentBuilder dbuilder = dbfac.newDocumentBuilder();
	        
	        Document doc = dbuilder.parse(configfile);
	        Element config = (Element)doc.getElementsByTagName("MinecraftDatabase").item(0);
	        
	        NodeList classnames = config.getElementsByTagName("PluginClass");
	        if(classnames == null || classnames.getLength() == 0)
	        	throw new RuntimeException("No PluginClass specified.");
	        if(classnames.getLength() != 1)
	        	throw new RuntimeException("Exactly one PluginClass must be specified.");
	        	        
	        String classname = classnames.item(0).getTextContent(); //text from single element
	        
	        Class plugclass = loader.loadClass(classname);
	        
	        Method createInstance = plugclass.getDeclaredMethod("createInstance", File.class, Element.class, ClassLoader.class);
	        
	        if(!StorageProvider.class.isAssignableFrom(createInstance.getReturnType()))
	        	throw new RuntimeException("<PluginClass>.createInstance's return value must implement StorageProvider");
	        
	        NodeList plugOptionsList = config.getElementsByTagName("PluginOptions");
	        
	        Element plugOptions;
	        switch(plugOptionsList.getLength())
	        {
	        case 0:
	        	plugOptions = null;
	        	break;
	        	
	        case 1:
	        	plugOptions = (Element)plugOptionsList.item(0);
	        	break;
	        	
	        default:
	        	throw new RuntimeException("At most one PluginOptions element can be specified.");
	        }
            
            //create the base provider
            StorageProvider provider = (StorageProvider)createInstance.invoke(null, storage_dir, plugOptions, loader);
            
            //wrap in write cache, if requested
            NodeList writeCacheSpecifiers = config.getElementsByTagName("WriteCache");
            switch(writeCacheSpecifiers.getLength())
            {
            case 0:
                break;
                    
            case 1:
                Element writeCacheSpecifier = (Element)writeCacheSpecifiers.item(0);
                if(writeCacheSpecifier.hasChildNodes() || writeCacheSpecifier.hasAttributes())
                    throw new RuntimeException("There are currently no options for WriteCache");
                
                provider = new WriteBatcher(provider);
                break;
                
            default:
                throw new RuntimeException("WriteCache can be specified at most once.");
            }
            
            //wrap in read cache, if requested
            NodeList readCacheSpecifiers = config.getElementsByTagName("ReadCache");
            switch(readCacheSpecifiers.getLength())
            {
            case 0:
                break;
                    
            case 1:
                Element readCacheSpecifier = (Element)readCacheSpecifiers.item(0);
                
                boolean eager = readCacheSpecifier.getAttribute("eager").equals("true");
                
                provider = new ReadCache(provider, eager);
                break;
                
            default:
                throw new RuntimeException("ReadCache can be specified at most once.");
            }
	        
	        return provider;
		}
		else
		{
			System.out.println("Storageplug warning: no config file for " + storage_dir + " assuming MCRegion.");
			
			return new MCRegionStorageProvider(storage_dir);
		}
	}
	
	final static PluginLoader SINGLETON = new PluginLoader();
	
	public static PluginLoader getPluginLoader()
	{
		return SINGLETON;
	}
}
